package org.xqh.utils.map;

import lombok.extern.slf4j.Slf4j;
import org.gavaghan.geodesy.Ellipsoid;
import org.gavaghan.geodesy.GeodeticCalculator;
import org.gavaghan.geodesy.GeodeticCurve;
import org.gavaghan.geodesy.GlobalCoordinates;
import org.locationtech.jts.algorithm.PointLocator;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.operation.buffer.BufferOp;

/**
 * @ClassName LngLatDistanceUtils
 * @Description 经纬度 距离计算
 * @Author xuqianghui
 * @Date 2022/1/8 10:20
 * @Version 1.0
 */
@Slf4j
public class LngLatDistanceUtils {

    private static double EARTH_RADIUS = 6377.830;//地球半径(千米)


    public static void main(String[] args) {
        //121.717594,31.12055    121.817629,31.090867
//        double distance1 = getDistance1(31.12055, 121.717594, 31.090867, 121.817629);
//        log.info("getDistance1: {} 千米", distance1);
//
//        double distance2 = getDistance2(31.12055, 121.717594, 31.090867, 121.817629);
//        log.info("getDistance2: {} 千米", distance2);
//
//        double distance3 = getDistance3(31.12055, 121.717594, 31.090867, 121.817629);
//        log.info("getDistance3: {} 米", distance3);
//
//        double distance4 = getDistance4(31.12055, 121.717594, 31.090867, 121.817629);
//        log.info("getDistance4: {} 米", distance4);
//
//
//        // //121.717594,31.12055    121.817629,31.090867
//        GlobalCoordinates source = new GlobalCoordinates(31.12055, 121.717594);
//        GlobalCoordinates target = new GlobalCoordinates(31.090867, 121.817629);
//
//        double meter1 = getDistanceMeter(source, target, Ellipsoid.Sphere);
//        double meter2 = getDistanceMeter(source, target, Ellipsoid.WGS84);
//        log.info("getDistance5 Sphere坐标系计算结果: {} 米", meter1);
//        log.info("getDistance5 WGS84坐标系计算结果: {} 米", meter2);
        inCircle(121.139647f,28.85738f, 1l, 121.144625f, 28.859042f);
        System.out.println();
    }

    /// <summary>
    /// 角度换算成弧度
    /// </summary>
    /// <param name="d">角度</param>
    /// <returns>弧度</returns>
    private static double rad(double d) {
        return d * Math.PI / 180.0;
    }

    /// <summary>
    /// 计算距离
    /// </summary>
    /// <param name="lat1">纬度1</param>
    /// <param name="lng1">经度1</param>
    /// <param name="lat2">纬度2</param>
    /// <param name="lng2">经度2</param>
    /// <returns>距离（千米）</returns>
    public static double getDistance1(double lat1, double lng1, double lat2, double lng2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double radLng1 = rad(lng1);
        double radLng2 = rad(lng2);
        double s = Math.acos(Math.cos(radLat1) * Math.cos(radLat2) * Math.cos(radLng1 - radLng2) + Math.sin(radLat1) * Math.sin(radLat2));
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000) / 10000;
        return s;
    }


    /**
     * 方法二：
     */
/// <summary>
    /// 计算距离
    /// </summary>
    /// <param name="lat1">纬度1</param>
    /// <param name="lng1">经度1</param>
    /// <param name="lat2">纬度2</param>
    /// <param name="lng2">经度2</param>
    /// <returns>距离（千米）</returns>
    public static double getDistance2(double lat1, double lng1, double lat2, double lng2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double a = radLat1 - radLat2;
        double b = rad(lng1) - rad(lng2);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
                Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000) / 10000;
        return s;
    }


    /**
     * 根据某点A[lat,lng]，从地标库中查找周边N公里的地标。
     * <p>
     * 方法：根据点A算出经线上N公里的角度偏差，和纬线上N公里的角度偏差。然后根据最大最小经纬度，画出矩形范围。再遍历矩形范围中的坐标计算出周边N公里的地标。
     *
     * @param lat
     * @param distance
     */
    /// <summary>
    /// 根据距离计算某点角度偏差
    /// </summary>
    /// <param name="lat">纬度</param>
    /// <param name="distance">距离（千米）</param>
    /// <param name="latDeviation">纬度偏差</param>
    /// <param name="lngDeviation">经度偏差</param>
    public static void GetMaxDeviation(double lat, double distance) {
        double latDeviation = 0;
        double lngDeviation = 0;
        double radLat1 = rad(lat);
        //另一种根据纬度计算地球半径的写法，因为极地半径和赤道半径有偏差。
        //EARTH_RADIUS = 6356.9088 + 20.9212 * (90.0 - lat) / 90.0;
        double latRatio = 180 / (Math.PI * EARTH_RADIUS); //经线上1公里对应纬度偏差
        double lngRatio = latRatio / Math.cos(radLat1);//纬线上1公里对应经度偏差
        latDeviation = distance * latRatio;
        lngDeviation = distance * lngRatio;
        System.out.println(latDeviation + ", " + lngDeviation);
    }


    private static final double EARTH_RADIUS2 = 6371393; // 平均半径,单位：m；不是赤道半径。赤道为6378左右

    /**
     * @描述 反余弦进行计算
     * @参数 [lat1, lng1, lat2, lng2]
     * @返回值 double
     * @创建人 Young
     * @创建时间 2019/3/13 20:31
     **/
    public static double getDistance3(Double lat1, Double lng1, Double lat2, Double lng2) {
        // 经纬度（角度）转弧度。弧度用作参数，以调用Math.cos和Math.sin
        double radiansAX = Math.toRadians(lng1); // A经弧度
        double radiansAY = Math.toRadians(lat1); // A纬弧度
        double radiansBX = Math.toRadians(lng2); // B经弧度
        double radiansBY = Math.toRadians(lat2); // B纬弧度

        // 公式中“cosβ1cosβ2cos（α1-α2）+sinβ1sinβ2”的部分，得到∠AOB的cos值
        double cos = Math.cos(radiansAY) * Math.cos(radiansBY) * Math.cos(radiansAX - radiansBX)
                + Math.sin(radiansAY) * Math.sin(radiansBY);
//        System.out.println("cos = " + cos); // 值域[-1,1]
        double acos = Math.acos(cos); // 反余弦值
//        System.out.println("acos = " + acos); // 值域[0,π]
//        System.out.println("∠AOB = " + Math.toDegrees(acos)); // 球心角 值域[0,180]
        return EARTH_RADIUS2 * acos; // 最终结果
    }


    /**
     * @描述 经纬度获取距离，单位为米
     * @参数 [lat1, lng1, lat2, lng2]
     * @返回值 double
     * @创建人 Young
     * @创建时间 2019/3/13 20:33
     **/
    public static double getDistance4(double lat1, double lng1, double lat2, double lng2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double a = radLat1 - radLat2;
        double b = rad(lng1) - rad(lng2);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
                + Math.cos(radLat1) * Math.cos(radLat2)
                * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000d) / 10000d;
        s = s * 1000;
        return s;
    }


    public static double getDistanceMeter(GlobalCoordinates gpsFrom, GlobalCoordinates gpsTo, Ellipsoid ellipsoid) {
        //创建GeodeticCalculator，调用计算方法，传入坐标系、经纬度用于计算距离
        GeodeticCurve geoCurve = new GeodeticCalculator().calculateGeodeticCurve(ellipsoid, gpsFrom, gpsTo);

        return geoCurve.getEllipsoidalDistance();
    }

    /**
     * 判断经纬度是否在圆内
     *
     * @param lon       目标经度
     * @param lat       目标纬度
     * @param dis       距离，米
     * @param centerLon 圆心经度
     * @param centerLat 圆心纬度
     * @return
     */
    public static boolean inCircle(float lon, float lat, long dis, float centerLon, float centerLat) {
        //创建一条直线
        Coordinate[] coordinates4 = new Coordinate[]{
                new Coordinate(centerLon, centerLat),
                new Coordinate(centerLon, centerLon + 0.01f * dis / 1000),
        };

        GeometryFactory gf = new GeometryFactory();
        Geometry gfLineString = gf.createLineString(coordinates4);
        double degree = dis / (2 * Math.PI * 6371004) * 360; //将度换算成米，公式为：degree = meter / (2 * Math.PI * 6371004) * 360
        //缓冲区建立
        BufferOp bufOp = new BufferOp(gfLineString);
        bufOp.setEndCapStyle(BufferOp.CAP_BUTT);
        Geometry bg = bufOp.getResultGeometry(degree);
        //点是否在多边形内判断
        Coordinate point = new Coordinate(lon, lat);
        PointLocator a = new PointLocator();
        boolean p1 = a.intersects(point, bg);
        if (p1) {
            System.out.println("point1:" + "该点在多边形内" + p1);
        } else {
            System.out.println("point1:" + "该点不在多边形内" + p1);
        }
        return p1;
    }


}
